using System.Collections.Generic;
using System.Threading.Tasks;
using Godot;

namespace MyCraft2;

[Tool]
public partial class ChunkManager : Node
{
    public override void _Ready()
    {
        base._Ready();

        _chunkManager ??= this;

        const int UPPER = 16;
        Task.Run(delegate
        {
            for (var x = 0; x < UPPER; x++)
            for (var z = 0; z < UPPER; z++)
            for (var y = 0; y < 5; y++)
                SetBlock(new Vector3I(x, y, z), y is 4 ? BlockInstances.Grass : BlockInstances.Stone);

            for (var x = -1; x > -UPPER; x--)
            for (var z = -1; z > -UPPER; z--)
            for (var y = 0; y < 5; y++)
                SetBlock(new Vector3I(x, y, z), y is 4 ? BlockInstances.Grass : BlockInstances.Stone);

            for (var x = 0; x < UPPER; x++)
            for (var z = -1; z > -UPPER; z--)
            for (var y = 0; y < 5; y++)
                SetBlock(new Vector3I(x, y, z), y is 4 ? BlockInstances.Grass : BlockInstances.Stone);

            for (var x = -1; x > -UPPER; x--)
            for (var z = 0; z < UPPER; z++)
            for (var y = 0; y < 5; y++)
                SetBlock(new Vector3I(x, y, z), y is 4 ? BlockInstances.Grass : BlockInstances.Stone);

            for(var y = 0; y < 5; y++)
                SetBlock(new Vector3I(0,y,0), BlockInstances.OakPlank);
            
            foreach (var chunk in Chunks)
            {
                chunk.Value.Commit();
                Callable.From(delegate
                {
                    AddChild(chunk.Value);
                    if (chunk.Key == Vector2I.Zero)
                        GetNode<CharacterBody3D>("../Player").Position = new Vector3(0, 4.5f, 0);
                }).CallDeferred();
            }
        });
    }

    public override void _Process(double delta)
    {
        base._Process(delta);

        foreach (var chunk in ChunksPendingLoad)
            AddChild(chunk);
        ChunksPendingLoad.Clear();
    }

    private static Chunk LoadTestChunk(Vector2I chunkPosition)
    {
        lock (Chunks)
        {
            var chunk = new Chunk();
            for (var x = chunkPosition.X * ChunkSize; x < ChunkSize + chunkPosition.X * ChunkSize; x++)
            for (var z = chunkPosition.Y * ChunkSize; z < ChunkSize + chunkPosition.Y * ChunkSize; z++)
            for (var y = 0; y < 5; y++)
                chunk.SetBlock(new Vector3I(x, y, z), y == 4 ? BlockInstances.Grass : BlockInstances.Stone);

            Chunks[chunkPosition] = chunk;
            return chunk;
        }
    }
}

public partial class ChunkManager
{
    public const int ChunkSize = 16;
    private static ChunkManager? _chunkManager;
    private static readonly Dictionary<Vector2I, Chunk> Chunks = [];
    private static readonly List<Chunk> ChunksPendingLoad = [];
    
    public static void SetBlock(Vector3I position, Block? block, bool commit = false,
        bool commitAdjacentChunkTier = false)
    {
        var chunkPosition = GetChunkPosition(position);
        if (!Chunks.TryGetValue(chunkPosition, out var chunk))
        {
            chunk = new Chunk();
            Chunks[chunkPosition] = chunk;
           if(commit)ChunksPendingLoad.Add(chunk); 
        }

        chunk.SetBlock(position, block, commit, commitAdjacentChunkTier);
        if (commitAdjacentChunkTier)
        {
            if (Chunks.TryGetValue(chunkPosition + Vector2I.Up, out chunk)) chunk.Commit(position.Y);
            if (Chunks.TryGetValue(chunkPosition + Vector2I.Down, out chunk)) chunk.Commit(position.Y);
            if (Chunks.TryGetValue(chunkPosition + Vector2I.Left, out chunk)) chunk.Commit(position.Y);
            if (Chunks.TryGetValue(chunkPosition + Vector2I.Right, out chunk)) chunk.Commit(position.Y);
        }
    }

    public static Block? GetBlock(Vector3I position)
    {
        var chunkPosition = GetChunkPosition(position);
        return Chunks.TryGetValue(chunkPosition, out var chunk) ? chunk.GetBlock(position) : null;
    }

    public static Vector2I GetChunkPosition(Vector3I position)
    {
        int x;
        int y;
        if (position.X >= 0) x = position.X / 16;
        else
        {
            x = position.X / 16 - 1;
            if (position.X % 16 == 0) x += 1;
        }
        
        if (position.Z >= 0) y = position.Z / 16;
        else
        {
            y = position.Z / 16 - 1;
            if (position.Z % 16 == 0) y += 1;
        }
        
        return new Vector2I(x, y);
    }
}